package top.lingma.handler;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.integration.support.locks.LockRegistry;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import top.lingma.algorithm.RedisRateLimiter;
import top.lingma.annotation.RateLimit;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * @author yui
 * @date 2019年05月20日 下午5:34
 */
@Aspect
@Component
public class LimitAspect {

    private static final ExpressionParser expressionParser = new SpelExpressionParser();
    ParameterNameDiscoverer defaultParameterNameDiscoverer = new DefaultParameterNameDiscoverer();

    private ConcurrentHashMap<String, Expression> cacheExpression = new ConcurrentHashMap<>();

    /**
     * 本地持有对象
     */
    private volatile Map<String, RedisRateLimiter> rateLimiterMap = new ConcurrentHashMap<>();

    @Autowired
    LockRegistry syncLock;

    @Autowired
    RedisTemplate redisTemplate;

    @Around("@annotation(limit)")
    public void before(ProceedingJoinPoint joinPoint, RateLimit limit) {
        try {


            Signature sig = joinPoint.getSignature();
            if (!(sig instanceof MethodSignature)) {
                throw new IllegalArgumentException("该注解只能用在方法上");
            }

            // 切面所在类
            Object target = joinPoint.getTarget();
            Method method = ((MethodSignature) sig).getMethod();
            Method realMethod = target.getClass().getDeclaredMethod(sig.getName(), method.getParameterTypes());
            //此处realMethod是目标对象（原始的）的方法
            //   Annotation an = method.getAnnotation(UserChangeLog.class);


            EvaluationContext evaluationContext = new MethodBasedEvaluationContext(target, method, joinPoint.getArgs(),
                    defaultParameterNameDiscoverer);

            // 组装完整key
            String key = limit.prefix().concat(this.getExpression(limit.key(), evaluationContext));
            RedisRateLimiter orCreate = getOrCreateRateLimiter(key, joinPoint, limit);
            Boolean aBoolean = orCreate.tryAcquire(limit.timeout(), TimeUnit.MILLISECONDS);
            if (aBoolean) {
                joinPoint.proceed();
            }

        } catch (Exception e) {
            // e.printStackTrace();
        } catch (Throwable throwable) {
            //throwable.printStackTrace();
        }

    }


    /**
     * 构造或获取 令牌桶限流器
     *
     * @param key redis key
     * @return
     */
    public RedisRateLimiter getOrCreateRateLimiter(String key, JoinPoint joinPoint, RateLimit limit) {
        if (!rateLimiterMap.containsKey(key)) {
            synchronized (this) {
                if (!rateLimiterMap.containsKey(key)) {
                    RedisRateLimiter redisRateLimiter = new RedisRateLimiter(key, limit.permitsPerSecond(), limit.maxBurstSeconds(), redisTemplate, syncLock);
                    redisRateLimiter.setLimit(limit);
                    redisRateLimiter.setJoinPoint(joinPoint);
                    try {
                        //  redisRateLimiter.setTokenInsufficient(limit.tokenInsufficient().newInstance());
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    rateLimiterMap.put(key, redisRateLimiter);
                }
            }
        }
        return rateLimiterMap.get(key);
    }


    private String getExpression(String expression, EvaluationContext evaluationContext) {
        if (StringUtils.isEmpty(expression)) {
            return "";
        }
        if (cacheExpression.containsKey(expression)) {
            return cacheExpression.get(expression).getValue(evaluationContext, String.class);
        } else {
            Expression expressionObject = null;
            synchronized (LimitAspect.class) {
                expressionObject = expressionParser.parseExpression(expression);
            }
            cacheExpression.put(expression, expressionObject);
            return expressionObject.getValue(evaluationContext, String.class);
        }

    }

}
